; 代码清单8-1
; 文件说明: 主引导扇区代码
; 创建时间: 2024-09-23 22:12

    app_lba_start equ 100       ; 用户程序所在硬盘起始扇区号

SECTION mbr align=16 vstart=0x7c00  ; 偏移地址为0x7c00
    mov ax, 0
    mov ss, ax      ; 初始化栈地址(段地址/栈指针)
    mov sp, ax

    mov ax, [phy_base]
    mov dx, [phy_base+2]
    mov bx, 16                  ; 计算用户加载程序的逻辑段地址
    div bx

    mov ds, ax                  ; 令DS和ES指向该段地址
    mov es, ax

    ; 读取一个扇区
    xor di, di
    mov si, app_lba_start       ; 初始化参数 di:si=起始扇区号, ds:bx=目的缓冲区
    xor bx, bx
    call read_hard_disk_0

    ; 判断程序大小
    mov ax, [0x00]
    mov dx, [0x02]
    mov bx, 512             ; 判断程序有多少个扇区
    div bx

    cmp dx, 0               ; 判断是否整除
    jnz @1
    dec ax                  ; 整除扇区数-1

@1:
    cmp ax, 0
    jz direct

    ; 读取剩余的扇区
    push ds
    mov cx, ax

@2:
    mov ax, ds
    add ax, 0x20        ; 以512字节作为一个段
    mov ds, ax

    xor bx, bx
    inc si                  ; 循环读取一个扇区数据, 将用户程序加载到给定的物理地址处
    call read_hard_disk_0
    loop @2

    pop ds

; 计算程序入口地址
direct:
    mov ax, [0x06]
    mov dx, [0x08]
    call calc_segment_base
    mov [0x06], ax              ; 回填重定位后的程序入口段地址

    ; 重定位段地址表
    mov cx, [0x0a]              ; 需要重定位段地址表项数
    mov bx, 0x0c                ; 重定位段地址表首地址

realloc:
    mov ax, [bx]
    mov dx, [bx+0x02]
    call calc_segment_base
    mov [bx], ax                ; 回填段的基址
    add bx, 4                   ; 下一个重定位项
    loop realloc

; 转移到用户程序
    jmp far [0x04]              ; 程序入口 偏移地址[0x04], 段地址[0x06]

; -------------------读取一个硬盘扇区-----------------------
; 输入:   DI:SI = 起始逻辑扇区号
; 输出:   DS:BX = 目标缓冲区地址
read_hard_disk_0:
    push ax
    push bx
    push cx
    push dx

    ; 设置读取扇区数
    mov dx, 0x1f2
    mov al, 1           ; 读取1个扇区
    out dx, al

    ; 设置读取扇区起始逻辑号
    inc dx              ; 0x1f3
    mov ax, si
    out dx, al          ; LBA地址 0~7

    inc dx              ; 0x1f4
    mov al, ah
    out dx, al          ; LBA地址 8~15

    inc dx              ; 0x1f5
    mov ax, di
    out dx, al          ; LBA地址 16~23

    inc dx              ; 0x1f6
    mov al, 0xe0        ; LBA模式, 主盘
    or al, ah
    out dx, al          ; LBA地址: 24~28

    ; 设置读模式
    inc dx              ; 0x1f7
    mov al, 0x20        ; 0x20读模式, 0x30写模式
    out dx, al

; 等待硬盘数据准备完成
.waits:
    in al, dx           ; 读取0x1f7数据
    and al, 0x88        ; 只保留3和7位数据,其余清零
    cmp al, 0x08        ; 判断硬盘是否准备完成
    jnz .waits          ; 继续等待

    ; 读取512字节数据
    mov cx, 256
    mov dx, 0x1f0
.readw:
    in ax, dx           ; 从硬盘读取一个字
    mov [bx], ax        ; 写入缓冲区
    add bx, 2
    loop .readw

    pop dx
    pop cx
    pop bx
    pop ax

    ret

; ----------------------计算16位逻辑段地址-------------------------
; 输入:   DX:AX = 32位物理地址
; 输出:   AX = 16为段基地址
calc_segment_base:
    push dx

    add ax, [cs:phy_base]
    adc dx, [cs:phy_base+0x02]
    shr ax, 4
    ror dx, 4
    and dx, 0xf000
    or ax, dx

    pop dx
    ret


    phy_base: dd 0x10000        ; 用户程序加载的内存起始位置

    times 510-($-$$) db 0
                    db 0x55, 0xaa